#include <iostream>
#include <map>

#include <include/core/SkCanvas.h>
#include <include/core/SkDrawable.h>
#include <include/core/SkPath.h>
#include <include/core/SkPicture.h>
#include <include/core/SkRegion.h>
#include <include/core/SkTextBlob.h>
#include <skia_control/button.h>
#include <skia_control/text.h>
#include <skia_framework.h>
#include <include/utils/SkNoDrawCanvas.h>

class OperationListener {
public:
    OperationListener(SkCanvas &canvas) : canvas_(canvas) {}
    virtual void onDrawPaint(const SkPaint& paint) {}
    virtual void onDrawBehind(const SkPaint&) {}
    virtual void onDrawRect(const SkRect& rect, const SkPaint& paint) {}
    virtual void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {}
    virtual void onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
                              const SkPaint& paint) {}
    virtual void onDrawOval(const SkRect& rect, const SkPaint& paint) {}
    virtual void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
                           const SkPaint& paint) {}
    virtual void onDrawPath(const SkPath& path, const SkPaint& paint) {}
    virtual void onDrawRegion(const SkRegion& region, const SkPaint& paint) {}
    virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                const SkPaint& paint) {}
    virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                             const SkPoint texCoords[4], SkBlendMode mode,
                             const SkPaint& paint) {}
    virtual void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
                              const SkPaint& paint) {}
    virtual void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
                    SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) {}
    virtual void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) {}
    virtual void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) {}
    virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) {}
    virtual void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                               const SkPaint* paint) {}
    virtual void onFlush() {}

protected:
    SkCanvas &canvas_;
};

class OverdrawListenedCanvas : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
public:
    OverdrawListenedCanvas(SkCanvas &canvas, OperationListener &listener)
        : SkCanvasVirtualEnforcer<SkNoDrawCanvas>(canvas.imageInfo().width(), canvas.imageInfo().height()),
        canvas_(canvas), listener_(listener)
    {
    }

    void onDrawPaint(const SkPaint& paint) override
    {
        listener_.onDrawPaint(paint);
        canvas_.drawPaint(paint);
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override
    {
        listener_.onDrawRect(rect, paint);
        canvas_.drawRect(rect, paint);
    }

    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override
    {
        listener_.onDrawRRect(rrect, paint);
        canvas_.drawRRect(rrect, paint);
    }

    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
                      const SkPaint& paint) override
    {
        listener_.onDrawDRRect(outer, inner, paint);
        canvas_.drawDRRect(outer, inner, paint);
    }

    void onDrawOval(const SkRect& rect, const SkPaint& paint) override
    {
        listener_.onDrawOval(rect, paint);
        canvas_.drawOval(rect, paint);
    }

    void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
                   const SkPaint& paint) override
    {
        listener_.onDrawArc(rect, startAngle, sweepAngle, useCenter, paint);
        canvas_.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
    }

    void onDrawPath(const SkPath& path, const SkPaint& paint) override
    {
        listener_.onDrawPath(path, paint);
        canvas_.drawPath(path, paint);
    }

    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override
    {
        listener_.onDrawRegion(region, paint);
        canvas_.drawRegion(region, paint);
    }

    void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                        const SkPaint& paint) override
    {
        listener_.onDrawTextBlob(blob, x, y, paint);
        canvas_.drawTextBlob(blob, x, y, paint);
    }

    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                     const SkPoint texCoords[4], SkBlendMode mode,
                     const SkPaint& paint) override
    {
        listener_.onDrawPatch(cubics, colors, texCoords, mode, paint);
        canvas_.drawPatch(cubics, colors, texCoords, mode, paint);
    }

    void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
                      const SkPaint& paint) override
    {
        listener_.onDrawPoints(mode, count, pts, paint);
        canvas_.drawPoints(mode, count, pts, paint);
    }

    void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
            SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) override
    {
        listener_.onDrawEdgeAAQuad(rect, clip, aaFlags, color, mode);
        canvas_.experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, mode);
    }

    void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override
    {
        listener_.onDrawAnnotation(rect, key, value);
        canvas_.drawAnnotation(rect, key, value);
    }

    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rect) override
    {
        listener_.onDrawShadowRec(path, rect);
        canvas_.private_draw_shadow_rec(path, rect);
    }

    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override
    {
        listener_.onDrawDrawable(drawable, matrix);
        canvas_.drawDrawable(drawable, matrix);
    }

    void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                       const SkPaint* paint) override
    {
        listener_.onDrawPicture(picture, matrix, paint);
        canvas_.drawPicture(picture, matrix, paint);
    }

    void willSave() override
    {
        canvas_.save();
    }

    void willRestore() override
    {
        canvas_.restore();
    }

    void onFlush() override
    {
        listener_.onFlush();
        canvas_.flush();
    }

    void didTranslate(SkScalar dx, SkScalar dy) override
    {
        canvas_.translate(dx, dy);
    }

    void onClipRect(const SkRect& rect, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipRect(rect, clipOp, style);
    }

    void onClipRRect(const SkRRect& rect, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipRRect(rect, clipOp, style);
    }

    void onClipPath(const SkPath& path, SkClipOp clipOp, ClipEdgeStyle style) override
    {
        canvas_.clipPath(path, clipOp, style);
    }

    void onClipRegion(const SkRegion& region, SkClipOp clipop) override
    {
        canvas_.clipRegion(region, clipop);
    }

private:
    SkCanvas &canvas_;
    OperationListener &listener_;
};

class OverdrawListener : public OperationListener {
public:
    OverdrawListener(SkCanvas &canvas)
        : OperationListener(canvas)
    {
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override
    {
        SkPath path;
        path.addRect(rect);
        AppendRegion(path);
    }

    void onDrawRRect(const SkRRect& rect, const SkPaint& paint) override
    {
        SkPath path;
        path.addRRect(rect);
        AppendRegion(path);
    }

    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
                      const SkPaint& paint) override
    {
        SkPath path;
        path.addRRect(outer);
        path.addRRect(inner);
        path.setFillType(SkPathFillType::kEvenOdd);
        path.setIsVolatile(true);
        AppendRegion(path);
    }

    void onDrawOval(const SkRect& rect, const SkPaint& paint) override
    {
        SkPath path;
        path.addOval(rect);
        AppendRegion(path);
    }

    void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
                   const SkPaint& paint) override
    {
        SkPath path;
        path.addArc(rect, startAngle, sweepAngle);
        AppendRegion(path);
    }

    void onDrawPath(const SkPath& path, const SkPaint& paint) override
    {
        SkPath tpath = path;
        AppendRegion(tpath);
    }

    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override
    {
        SkPath path;
        region.getBoundaryPath(&path);
        AppendRegion(path);
    }

    void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                        const SkPaint& paint) override
    {
        if (blob == nullptr) {
            return;
        }

        auto rect = blob->bounds();
        rect.offset(x, y);
        onDrawRect(rect, paint);
    }

    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                     const SkPoint texCoords[4], SkBlendMode mode,
                     const SkPaint& paint) override
    {
        // TODO
    }

    void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[],
                      const SkPaint& paint) override
    {
        for (int i = 0; i < count; i++) {
            onDrawRect(SkRect::MakeXYWH(pts[i].x(), pts[i].y(), 1, 1), paint);
        }
    }

    void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4],
            SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) override
    {
        SkPaint paint{color};
        paint.setBlendMode(mode);
        if (clip) {
            SkPath clipPath;
            clipPath.addPoly(clip, 4, true);
            onDrawPath(clipPath, paint);
        } else {
            onDrawRect(rect, paint);
        }
    }

    void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override
    {
        // TODO
    }

    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rect) override
    {
        // TODO
    }

    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override
    {
        if (drawable == nullptr) {
            return;
        }

        canvas_.save();
        if (matrix) {
            canvas_.setMatrix(canvas_.getTotalMatrix().postConcat(*matrix));
        }

        onDrawRect(drawable->getBounds(), {});
        canvas_.restore();
    }

    void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                       const SkPaint* paint) override
    {
        if (picture == nullptr) {
            return;
        }

        canvas_.save();
        if (matrix) {
            canvas_.setMatrix(canvas_.getTotalMatrix().postConcat(*matrix));
        }

        SkPaint p;
        if (paint) {
            p = *paint;
        }

        onDrawRect(picture->cullRect(), p);
        canvas_.restore();
    }

    void onFlush() override
    {
        Draw(canvas_);
    }

    void AppendRegion(SkPath &path)
    {
        SkRegion target;
        path.transform(canvas_.getTotalMatrix());
        target.setPath(path, SkRegion(SkIRect::MakeLTRB(-1e9, -1e9, 1e9, 1e9)));

        for (int32_t i = regions_.size(); i > 0; i--) {
            if (regions_[i].intersects(target)) {
                auto regi = regions_[i];
                regi.op(target, SkRegion::kIntersect_Op);
                regions_[i + 1].op(regi, SkRegion::kUnion_Op);
            }
        }
        regions_[1].op(target, SkRegion::kUnion_Op);
    }

    void Draw(SkCanvas &canvas)
    {
        static const std::map<int, SkColor> overdrawColorMap = {
            {1, 0x00000000},
            {2, 0x220000ff},
            {3, 0x2200ff00},
            {4, 0x22ff0000},
        };

        SkPaint paint;
        paint.setAntiAlias(true);
        paint.setStyle(paint.kFill_Style);

        SkRegion drawed;
        for (int32_t i = regions_.size(); i > 0; i--) {
            if (overdrawColorMap.find(i) != overdrawColorMap.end()) {
                paint.setColor(overdrawColorMap.at(i));
            } else {
                paint.setColor(0x44ff0000);
            }
            auto todo = regions_[i];
            todo.op(drawed, SkRegion::kDifference_Op);
            canvas.drawRegion(todo, paint);
            drawed.op(todo, SkRegion::kUnion_Op);
        }
    }

    void Dump()
    {
        SkRegion drawed;
        for (int32_t i = regions_.size(); i > 0; i--) {
            auto todo = regions_[i];
            todo.op(drawed, SkRegion::kDifference_Op);
            for (SkRegion::Iterator it(todo); it.done() == false; it.next()) {
                auto &rect = it.rect();
                printf("%d: %d,%d %dx%d\n", i, rect.left(), rect.top(), rect.width(), rect.height());
            }
            drawed.op(todo, SkRegion::kUnion_Op);
        }
    }

private:
    std::map<int, SkRegion> regions_;
};

int main()
{
    SkiaFramework sf;
    bool debugOverdraw = false;

    SkiaControl::Button::Builder buttonBuilder;
    buttonBuilder.SetText("switch debugOverdraw");
    buttonBuilder.SetRegion(SkRect::MakeXYWH(30, 30, 0, 24));

    SkiaControl::Text::Builder textBuilder;
    textBuilder.SetText("debugOverdraw: false");
    textBuilder.SetRegion(SkRect::MakeXYWH(30, 60, 0, 0));
    auto text = textBuilder.Build();

    auto button = buttonBuilder.Build();
    button->SetOnClickFunc([&]() {
        debugOverdraw = !debugOverdraw;
        text->SetText(debugOverdraw ? "debugOverdraw: true" : "debugOverdraw: false");
    });

    sf.AddControl(button);
    sf.AddControl(text);
    sf.SetDrawFunc([&](SkCanvas &c) {
        OverdrawListener listener(c);

        SkCanvas *canvas = &c;
        if (debugOverdraw) {
            canvas = new OverdrawListenedCanvas(c, listener);
        }

        canvas->save();
        canvas->translate(100, 100);

        SkPaint paint;
        paint.setAntiAlias(true);
        paint.setStyle(paint.kStrokeAndFill_Style);
        paint.setColor(0xffcccccc);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 400, 400), paint);
        paint.setColor(0xffdddddd);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 300, 300), paint);
        paint.setColor(0xffeeeeee);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
        paint.setColor(0xffffffff);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
        paint.setColor(0xffcccccc);
        canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 200), paint);
        paint.setColor(0xff888888);
        canvas->drawRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(50, 50, 400, 300), 10, 10), paint);

        canvas->restore();
        canvas->flush();

        if (debugOverdraw) {
            delete canvas;
        }
    });
    sf.Run();
    return 0;
}
